﻿using System;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System.IO;
using System.Threading;
using StartupTaskRunner.Configuration;
using StartupTaskRunner.Logging;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace StartupTaskRunner
{
    internal class BlobStorageDAL : IStorageDAL
    {
        private CloudStorageAccount _azureStorageAccount;
        private ManualResetEvent downloadEnded;

        public BlobStorageDAL()
        {
            BlobStorageType blobStorageConfig = ConfigType.GetBlobStorage();

            if (ConfigType.GetConfiguration().DevelopmentMode)
            {
                _azureStorageAccount = CloudStorageAccount.DevelopmentStorageAccount;
            }
            else
            {
                StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(blobStorageConfig.name, blobStorageConfig.key);
                _azureStorageAccount = new CloudStorageAccount(credentials, true);
            }

            downloadEnded = new ManualResetEvent(false);
        }

        public string GetConnectionString()
        {
            return RoleEnvironment.GetConfigurationSettingValue("StartupTaskRunner.ConnectionString");
        }

        public string Getcontainer()
        {
            return RoleEnvironment.GetConfigurationSettingValue("StartupTaskRunner.StartupResourcesContainer");
        }

        public string GetconfigurationFilename()
        {
            return RoleEnvironment.GetConfigurationSettingValue("StartupTaskRunner.ConfigurationFilename");
        }

        /// <summary>
        /// Downlaods a resource from blob storage to s[pecified location
        /// </summary>
        /// <param name="containerName"></param>
        /// <param name="blobResource"></param>
        /// <returns></returns>
        public void DownloadFileFromBlob(string destinationFolder, string containerName, string blobResource, bool overwrite)
        {
            CloudBlobClient client = _azureStorageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = client.GetContainerReference(containerName);
            CloudBlob blob = container.GetBlobReference(blobResource);

            // A blobResource might mimic a folder structure
            string absolutePath = GetPath(destinationFolder, blobResource);

            if (File.Exists(absolutePath))
            {
                if (overwrite)
                {
                    File.Delete(absolutePath);
                }
                else
                {
                    Logger.WriteMessage("File {0} exists and is not overwritten", blobResource);
                    return;
                }
            }

            downloadEnded.Reset();
            FileStream downloadStream = null;
            try
            {
                downloadStream = new FileStream(absolutePath, FileMode.CreateNew);
                blob.BeginDownloadToStream(downloadStream, this.DownloadBlobToStreamCallback, blob);
                // Wait for downlaod to end
                downloadEnded.WaitOne();
            }
            catch (Exception ex)
            {
                Logger.LogException(ex, "Error downloading resource {0} from blob storage.", blob.Uri);
            }
            finally
            {
                if (downloadStream != null)
                {
                    downloadStream.Close();
                }
            }
        }

        /// <summary>
        /// Uplaods a file to blob
        /// </summary>
        /// <param name="sourceFile">relative or absolute path to the file to be uplaoded</param>
        /// <param name="containerName">Conatainer to upload</param>
        /// <param name="blobResource">Relative URI to the blob resource</param>
        public void UploadFileToBlob(string sourceFile, string containerName, string blobResource)
        {
            CloudBlobClient client = _azureStorageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = client.GetContainerReference(containerName);
            CloudBlob blob = container.GetBlobReference(blobResource);
            blob.UploadFile(sourceFile);
        }

        public string DownloadFileContentFromBlob(string connectionString, string containerName, string configurationFilename)
        {
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudBlobClient client = storageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = client.GetContainerReference(containerName);
            CloudBlob blob = container.GetBlobReference(configurationFilename);

            string content = blob.DownloadText();
            return content;
        }

        public void UnZipFromBlobStorage(string containerName, string archiveName, string destinationFolder, bool overwrite)
        {
            string container = containerName ?? ConfigType.GetConfiguration().BlobStorage.defaultContainer;
            CloudBlobClient client = _azureStorageAccount.CreateCloudBlobClient();
            CloudBlobDirectory directory = client.GetBlobDirectoryReference(container);

            if (string.IsNullOrEmpty(archiveName))  //Unzip all archives in the directory...
            {
                foreach (IListBlobItem item in directory.ListBlobs())
                {
                    string blobUri = item.Uri.AbsoluteUri;
                    if (!UnzipBlob(directory, blobUri, destinationFolder, overwrite))
                    {
                        Logger.WriteMessage("Failed to unzip archive '{0}' from directory '{1}' to destination '{2}'.", archiveName, containerName, destinationFolder);
                    }
                }
            }
            else  //Unzip the speicified archive...
            {
                string blobUri = directory.Uri + "/" + archiveName;
                if (!UnzipBlob(directory, blobUri, destinationFolder, overwrite))
                {
                    Logger.WriteMessage("Failed to unzip archive '{0}' from directory '{1}' to destination '{2}'.", archiveName, containerName, destinationFolder);
                }
            }   
        }

        private bool UnzipBlob(CloudBlobDirectory directory, string blobUri, string destinationFolder, bool overwrite)
        {
            bool success = true;
            try
            {
                CloudBlob archive = directory.GetBlobReference(blobUri);
                using (MemoryStream stream = new MemoryStream())
                {
                    archive.DownloadToStream(stream);
                    stream.Seek(0, SeekOrigin.Begin);
                    ZipHelper.ExtractArchiveToFileSystem(stream, destinationFolder, overwrite);
                }
            }
            catch (Exception ex)
            {
                success = false;
                Logger.LogException(ex);
            }

            return success;
        }

        private string GetPath(string destinationFolder, string blobResource)
        {
            int index = blobResource.LastIndexOf("/");

            if (index >= 0)
            {
                string subfolder = blobResource.Substring(0, index);
                string fullFolderPath;
                if (Path.IsPathRooted(destinationFolder))
                {
                    fullFolderPath = string.Format("{0}/{1}", destinationFolder, subfolder);
                }
                else
                {
                    fullFolderPath = string.Format("{0}/{1}/{2}", Environment.CurrentDirectory, destinationFolder, subfolder);
                }

                Directory.CreateDirectory(fullFolderPath);
            }

            string absolutePath;
            if (Path.IsPathRooted(destinationFolder))
            {
                absolutePath = string.Format("{0}/{1}", destinationFolder, blobResource);
            }
            else
            {
                absolutePath = string.Format("{0}/{1}/{2}", Environment.CurrentDirectory, destinationFolder, blobResource);
            }

            return absolutePath.Replace(@"/", @"\");
        }

        internal void DownloadBlobToStreamCallback(IAsyncResult result)
        {
            CloudBlob blob = (CloudBlob)result.AsyncState;

            //End the operation.
            try
            {
                blob.EndDownloadToStream(result);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex, "Error downloading resource {0} from blob storage.", blob.Uri);
            }
            finally
            {
                downloadEnded.Set();
            }
        }
    }
}
